package cn.pm.datasource.sharding.algorithm;

import cn.pm.datasource.sharding.ShardingConfig;
import cn.pm.datasource.sharding.ShardingTableNameUtils;
import cn.pm.datasource.sharding.bean.AutoCreateTablesCacheBean;
import cn.pm.datasource.sharding.converter.DateConverter;
import com.google.common.collect.Range;
import lombok.SneakyThrows;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;

/**
 * Create by spf on 2019/12/30
 *
 * 本算法 按照分片键 半年分一次表
 * initMinYear 指最低初始化年份，所有小于此年份的数据都将被分配到同一张表中，目的是为了不让生成的表过多
 * 往后的数据将没半年分一次表
 */
@Component
public class YYYY01AutoCreateTablesShardingAlgorithm implements PreciseShardingAlgorithm, RangeShardingAlgorithm {
    private DateConverter dateConverter = new DateConverter();

    /**
     * 初始化最小年份
     * 为了控制生成实际表的数量，使实际表不会过多  所有小于此年份的数据都将被插入指定的表中，往后年份的数据将会按照半年进行分表
     */
    private static int initMinYear;
    @Resource
    public void setInitMinYear(ShardingConfig shardingConfig) {
        YYYY01AutoCreateTablesShardingAlgorithm.initMinYear = shardingConfig.getInitMinYear();
    }

    /**
     * 精确分片算法，用于=和IN。。该类需实现PreciseShardingAlgorithm接口并提供无参数的构造器
     */
    @SneakyThrows
    public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) {
        // 定义需要查询的表
        String realTableName = getDateTableName(shardingValue.getValue(), shardingValue.getLogicTableName());

        // 从缓存中获取数据库中已有的真实表列表
        AutoCreateTablesCacheBean table = ShardingTableNameUtils.getTable(shardingValue.getLogicTableName());
        if (table == null)
            throw new Exception("数据库动态创建表配置出错，请检查spring.shardingsphere.sharding.tables.逻辑表.table-strategy 相关配置");

        if (table.getRealTableNameList() != null && !table.getRealTableNameList().isEmpty()) {
            for (String targetName : table.getRealTableNameList()) {
                if (realTableName.equals(targetName)) {
                    return targetName;
                }
            }
        }

        // 已有的表中，没有参数所需要的表，创建
        ShardingTableNameUtils.createTable(realTableName, table);
        return realTableName;
    }

    /**
     * 范围分片算法，用于BETWEEN，可选。。该类需实现RangeShardingAlgorithm接口并提供无参数的构造器
     */
    @SneakyThrows
    public Collection<String> doSharding(Collection availableTargetNames, RangeShardingValue shardingValue) {
        Range valueRange = shardingValue.getValueRange();
        String startTableName = null;
        String endTableName = null;
        if (valueRange.hasLowerBound()) {
            Comparable startDate = valueRange.lowerEndpoint();
            startTableName = getDateTableName(startDate, shardingValue.getLogicTableName());
        }
        if (valueRange.hasUpperBound()) {
            Comparable endDate = valueRange.upperEndpoint();
            endTableName = getDateTableName(endDate, shardingValue.getLogicTableName());
        }

        AutoCreateTablesCacheBean table = ShardingTableNameUtils.getTable(shardingValue.getLogicTableName());
        if (table == null)
            throw new Exception("数据库动态创建表配置出错，请检查spring.shardingsphere.sharding.tables.逻辑表.table-strategy 相关配置");

        List<String> result = new ArrayList<>();
        if (table.getRealTableNameList() != null && !table.getRealTableNameList().isEmpty()) {
            for (String targetTableName : table.getRealTableNameList()) {
                if (targetTableName == null)
                    continue;
                // 排除小于开始时间的表
                if (startTableName != null) {
                    if (targetTableName.compareTo(startTableName) < 0)
                        continue;
                }
                // 排除大于结束时间的表
                if (endTableName != null) {
                    if (targetTableName.compareTo(endTableName) > 0)
                        continue;
                }
                result.add(targetTableName);
            }
        }

        // 如果没有一张真实表符合逻辑，将返回母表
        if (result.isEmpty())
            result.add(table.getLogicTableName());
        return result;
    }

    // 根据日期参数，转换表名
    private String getDateTableName(Comparable params, String logicTable) throws Exception {
        if (params instanceof Date) {
            Date date = (Date) params;
            return getDateTableName(logicTable, date);
        } else if (params instanceof String) {
            Date date = dateConverter.convert(params.toString());
            if (date == null)
                throw new Exception("参数错误，参数:" + params.toString() + " 解析日期字段失败");
            return getDateTableName(logicTable, date);
        } else {
            throw new Exception( "分片器配置错误，本分片器仅用于处理日期类型字段");
        }
    }

    public static String getDateTableName(String logicTable, Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH);
        if (year <= YYYY01AutoCreateTablesShardingAlgorithm.initMinYear) {
            return logicTable + "_" + YYYY01AutoCreateTablesShardingAlgorithm.initMinYear;
        } else {
            if (month <= 5)
                return logicTable + "_" + year + "_0";
            else
                return logicTable + "_" + year + "_1";
        }
    }

}
